package com.nulldev.util.internal.testing;

import java.util.concurrent.atomic.AtomicReference;

public abstract class Test<T> implements Runnable {

	private final AtomicReference<T> expectedResult = new AtomicReference<T>();
	private final AtomicReference<T> result = new AtomicReference<T>();
	private final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
	private final AtomicReference<TestResult> testResult = new AtomicReference<TestResult>(TestResult.QUEUED);
	private final AtomicReference<FailureReason> failReason = new AtomicReference<FailureReason>();

	public Test() {
	}

	public Test(final T expectedResult) {
		this.expectedResult.lazySet(expectedResult);
	}

	public Test(final TestResult tResult) {
		this.testResult.lazySet(tResult);
	}

	@Override
	public void run() {
		try {
			if (this.testResult.get() == TestResult.DISABLED)
				return;
			this.test();
		} catch (Throwable e) {
			this.error.lazySet(e);
			this.testResult.set(TestResult.CRASHED);
		}
	}

	public abstract void test() throws Throwable;

	public boolean hasRun() {
		return this.testResult.get() != null && this.testResult.get() != TestResult.DISABLED;
	}

	protected void fail() {
		this.testResult.set(TestResult.FAILED);
	}

	protected void fail(final T value) {
		this.result.set(value);
		this.testResult.set(TestResult.FAILED);
		this.failReason.lazySet(FailureReason.FAIL_CAUSED_BY_TEST);
	}

	protected void fail(final T value, final Throwable err) {
		this.result.set(value);
		this.error.set(err);
		this.testResult.set(TestResult.FAILED);
		this.failReason.lazySet(FailureReason.INTERNAL_ERROR_CAUGHT);
	}

	protected void fail(final Throwable value) {
		this.error.set(value);
		this.testResult.set(TestResult.FAILED);
		this.failReason.lazySet(FailureReason.FAIL_CAUSED_BY_TEST);
	}

	@SuppressWarnings("unchecked")
	protected void str_finish(final String value) {
		this.result.set((T) value);
		if (this.expectedResult.get() == null) {
			this.testResult.set(TestResult.PASSED);
		} else if (!this.expectedResult.get().equals(this.result.get())) {
			this.testResult.set(TestResult.FAILED);
			this.failReason.lazySet(FailureReason.VALUE_MISMATCH);
		} else {
			this.testResult.set(TestResult.PASSED);
		}
	}

	protected void finish(final T value) {
		this.result.set(value);
		if (this.expectedResult.get() == null) {
			this.testResult.set(TestResult.PASSED);
		} else if (this.expectedResult.get() != this.result.get()) {
			this.testResult.set(TestResult.FAILED);
			this.failReason.lazySet(FailureReason.VALUE_MISMATCH);
		} else if (this.expectedResult.get() == this.result.get()) {
			this.testResult.set(TestResult.PASSED);
		}
	}

	public TestResult getResult() {
		return this.testResult.get();
	}

	public FailureReason getFailReason() {
		return this.failReason.get();
	}

	public Throwable getError() {
		return this.error.get();
	}

	public boolean hasError() {
		return this.error.get() != null;
	}

	public T getResultItem() {
		return this.result.get();
	}

	public T getExpectedResultItem() {
		return this.expectedResult.get();
	}
}
